/**
 * @file inputHandler.c
 * @brief Implementazione dell'entità InputHandler.
 *
 * Sono implementate le funzioni per gestire l'input dell'utente:
 * @see IH_waitAnInput è la funzione che attende un'input dalle sessioni registrate
 * tramite la chiamata di sistema select.
 *
 * @date Mar 14, 2012
 * @author Francesco Corvino
 */
#include "inputHandler.h"				/** inclusione delle definizioni dell'interfaccia */
#include "invoker.h"					/** si necessità di comunicare con l'invoker */
#include "../RscHost/factoryComm.h"		/** si necessita di comunicare con la factory per la gestione delle sessioni */
#include "../RscHost/session.h"			/** utilizzo delle sessioni */
#include "../logproxy/logproxy.h"		/** utilizzo del logging */

/**
 * @brief Variabile statica delle configurazioni di sistema.
 */
static InputHandler _ih_config;

/**
 * @brief Variabile statica privata per registrare i filtri aggiunti.
 *
 * Si differenzia dalla variabile presente in _ih_config poichè questa variabile contiene
 * le informazioni a runtime, _ih_config sono le informazioni di configurazione iniziale.
 */
static DynArray _ih_addedFilters;

static pthread_t _ih_thread = 0;

static bool _ih_isFilterInit = false;

/**
 * @brief Struttura privata utilizzata per registrare l'associazione tra
 * file descriptor delle sessioni e le stesse sessioni.
 */
typedef struct CoupleFdSession_ {
	/**
	 * @brief file descriptor in lettura della sessione.
	 */
	int fd;

	/**
	 * @brief puntatore alla sessione relativa al file descriptor.
	 */
	HostSession * ptrSession;
} CoupleFdSession;

/**
 * @brief Restituisce le sessioni di comunicazione con l'Host presenti.
 *
 * Funzione privata che ricava le sessioni disponibili ricavandole dai dati applicativi.
 */
CMap* _IH_getSessions(){
	return FactComm_getSessions();
}

/**
 * @brief Controlla che una sessione sia disponibile per inviare comandi.
 *
 * Se una sessione è occupata a dialogare con altri comandi, leggere l'input non corrisponderebbe
 * ad ottenere un comando. Funzione privata per permettere un miglior interfacciamento con le sessioni.
 *
 * @param session puntatore alla sessione da controllare
 * @return 1 se la sessione è pronta per inviare comandi, -1 altrimenti.
 */
bool _IH_isReadySession(HostSession* session){
	/// controlla se la sessione ha elaborazioni
	bool ret = !Session_hasElaboration(session);
	if(ret) return ret;
	/// controlla se la sessione era già pronta per la lettura
	if(session->isWaiting==0){ // sessione in attesa
		Session_Write(session,"ready>",6);
		session->isWaiting = 1;
		log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_DEBUG_LEVEL,
				"Sessione %d in attesa.",session->keySession);
	} else { // sessione occupata
		log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_DEBUG_LEVEL,
				"Sessione %d occupata.",session->keySession);
	}
	return ret;
}

/**
 * @brief Restituisce il File Descriptor in lettura di una sessione.
 *
 * Funzione privata, inserita per semplificare le modifiche verso le entità HostSession
 * sia dello stesso InputHandler.
 *
 * @param session sessione da cui ricavare il file descriptor.
 */
int _IH_getFdSession(HostSession* session) {
	return Session_getFDRead(session);
}

/**
 * @brief Ricava l'input dell'utente nella sessione passata come parametro.
 *
 * Funzione privata, inserita per semplificare le modifiche da e verso l'entità HostSession.
 *
 * @param req richiesta da inizializzare.
 * @param session sessione da cui ricavare il file descriptor.
 *
 * Operazioni svolte:
 */
void _IH_getRequestFrom(UserRequest* req, HostSession * session){
	char _IH_rtnMsg[SESSION_BUFFER_MESSAGE];
	/// - imposta che la stringa restituita sia terminata da '\0'
	memset(_IH_rtnMsg,0,SESSION_BUFFER_MESSAGE);
	/// - richiede la lettura dalla sessione
	Session_Read(session,_IH_rtnMsg,SESSION_BUFFER_MESSAGE);
	/// - inizializza la richiesta
	UserRequest_init(req,_IH_rtnMsg);
}

/**
 * @brief Funzione delegata per far partire il meccanismo di filtraggio che ritornerà l'oggetto DescRequest.
 *
 * L'input fornito dall'utente viene ricevuto come un puntatore char (una stringa)
 * per poter consentire una corretta elaborazione si applica questo meccanismo che
 * attua delle elaborazioni sulla stringa immessa a catena fino ad arrivare a una completa visione
 * parametrica del comando inviato dall'utente.
 *
 * @param req puntatore alla richiesta da filtrare.
 */
bool IH_getFilteredReq(UserRequest * req){
	log_namedlevel(LOG_INPUTHANDLER_NAME, LOG_INFO_LEVEL,
			"Filtri attivi: %d, richiesta pervenuta per la sessione %d ",
			_ih_addedFilters.numelement,req->session->keySession);
	int i = 0;
	UserRequest* tmp = req;
	InputFilter* f;
	DYNARRAY_FOREACH(&_ih_addedFilters,&i,f){
		log_simple("Avvio il filtro %d con in richiesta il comando <%s> ",
				i,req->nameCmd);
		tmp = (*(f->filter))(tmp);
		if(tmp == NULL) return false;
	}
	return true;
}

/**
 * @brief funzione per inserire un filtro alle richieste.
 *
 * Le varie richieste vengono poi richiamate a catena dalla funzione:
 * @see IH_getFilteredReq
 *
 * @param filter struttura dati rappresentante un filtro da applicare alla richiesta.
 */
void IH_addChainFilter(InputFilter * filter){
	if(!_ih_isFilterInit){ // verifica che l'array è stato inizializzato
		DynArray_init(&(_ih_addedFilters),NULL,sizeof(InputFilter));
		_ih_isFilterInit = true;
	}
	// aggiunge l'elemento
	DynArray_add(&(_ih_addedFilters),filter);
}

/**
 * @brief setter delle configurazioni dell'entità InputHandler.
 *
 * @param config puntatore a una struttura dati da cui copiare le configurazioni.
 */
void IH_setConfiguration(InputHandler * config){
	// copia i dati
	memcpy(&_ih_config, config, sizeof(InputHandler));
	// inizializza i filtri sulle richieste.
	if(!_ih_isFilterInit) {
		DynArray_init(&(_ih_addedFilters),NULL,sizeof(InputFilter));
		_ih_isFilterInit = true;
	}
	// aggiunge i filtri di configurazione.
	int i = 0; InputFilter * filter;
	DYNARRAY_FOREACH(&(config->requestfilters),&i,filter){
		DynArray_add(&_ih_addedFilters,filter);
		log_simple("Aggiungo un filtro a %d elementi, indice: %d ",
				_ih_addedFilters.numelement, i);
	}
	// sezione dedicata alla validazione dei parametri immessi
}

void _IH_handler(int sig){
	log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_INFO_LEVEL,
			"Gestisco il segnale %d ",sig);
}

int _IH_cmpCouples(const void *a, const void *b){
	CoupleFdSession *A = (CoupleFdSession*)a;
	CoupleFdSession *B = (CoupleFdSession*)b;
	return (A->fd>B->fd)?1:-1;
}

/**
 * @brief Funzione che attende l'arrivo di un'input.
 *
 * La funzione non accetta parametri ma è configurabile tramite il
 * passaggio diretto dei parametri di configurazione.
 *
 * @see IH_setConfiguration funzione che consente di modificare le impostazioni.
 */
void IH_waitAnInput(){
	DynArray couplesFdSession;
	DynArray_init(&couplesFdSession,_IH_cmpCouples,sizeof(CoupleFdSession));
	_ih_thread = pthread_self(); // registra la thread su cui è eseguito InputHandler
	sigset_t oldmask;		//utilizzata per riportare allo stato iniziale la signal mask
	sigset_t emptymask; sigemptyset(&emptymask);

	struct sigaction	act, oact;

	act.sa_handler = _IH_handler;
	sigfillset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGUSR1,&act,&oact);

	// configura il bloccaggio dei segnali
	if (sigprocmask(SIG_SETMASK, &(_ih_config.setInputSignals), &oldmask) < 0) {
		log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_ERROR_LEVEL,
				"Fallita chiamata sigprocmask, l'applicazione non può bloccare i segnali!");
	}

	do { /// Avvia un ciclo dove:
		log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_INFO_LEVEL,
				"ricava i fd da cui attende una scrittura");
		/// - si inizializzano le strutture: le sessioni, gli indici e i fd per la select
		CMap *sessions = _IH_getSessions();
		int iSession=0, maxfd=0; HostSession * session;

		// insieme di file descriptors per la select
		fd_set readySessions; FD_ZERO(&readySessions);

		/// - si controlla ogni sessione per vedere se:
		CMAP_FOREACH(sessions,&iSession,session){
			/// la sessione è pronta per la lettura
			if(!_IH_isReadySession(session)) continue;

			// recupera il file descripton della sessione iesima
			int fd = _IH_getFdSession(session);
			CoupleFdSession couple = {fd:fd, ptrSession: session};

			log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_INFO_LEVEL,
					"controlla la sessione %d", session->keySession);

			DynArray_add(&couplesFdSession,&couple);

			FD_SET(fd,&readySessions);
			// aggiorna il massimo;
			maxfd = (maxfd>fd)?maxfd:fd;
		}
		// effettua select
		int n =pselect(maxfd+1,
				&readySessions, NULL, NULL,
				&(_ih_config.timeToWaitInput),
				&emptymask);
				//&(_ih_config.setInputSignals));
		log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_INFO_LEVEL,
				"Esco dalla select.. ho %d input", n);
		if(n>0) { // l'input proviene da un file o socket
			iSession=0;
			CoupleFdSession* couple;
			// individua l'input da cui leggere
			DYNARRAY_FOREACH(&couplesFdSession,&iSession,couple){
				if(FD_ISSET(couple->fd,&readySessions)){
					UserRequest req;
					// avvia le operazioni di lettura efiltering
					_IH_getRequestFrom(&req,couple->ptrSession);
					req.session = couple->ptrSession;
					// richiede il filtraggio della richiesta
					if(IH_getFilteredReq(&req)){ // se il filtraggio si è completato
						// invoca la richiesta ottenuta
						Invoke_invokeCMD(&req);
					}
					break;
				} continue;
			}
		}
		DynArray_destroy(&couplesFdSession);
	} while(_ih_config.isLoop);
	sigprocmask(SIG_SETMASK,&oldmask,NULL);
}

/**
 * @brief Funzione che si occupa di aggiornare l'input.
 *
 * Contiene le operazioni per comunicare alla funzione IH_waitAnInput() di interrompere
 * l'attesa e di effettuare l'aggiornamento.
 *
 * @see IH_waitAnInput
 */
void IH_updateInput(){
	/// Effettua un controllo per vedere se l'inputHandler è stato avviato
	if(_ih_thread == 0) return;

	log_namedlevel(LOG_INPUTHANDLER_NAME,LOG_INFO_LEVEL,
			"Aggiorno l'input dell'utente.");

	/// Invia il segnale alla main thread
	pthread_kill(_ih_thread,_ih_config.updateSignal);
}
